package mspbots.tsheets.client.core.client;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.log4j.Log4j2;
import mspbots.tsheets.client.annotation.ApiException;
import mspbots.tsheets.client.core.TokenState;
import mspbots.tsheets.common.QueryParams;
import mspbots.tsheets.common.RabbitKeys;
import mspbots.tsheets.common.ResultSync;
import org.springframework.amqp.rabbit.core.RabbitMessagingTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.List;

/**
 * com.mspbots.sync.scheduled.tsheets.TSheetsUserGet
 *
 * @author Alex bob(https://github.com/vnobo)
 * @date Created by 2019/7/25
 */
@Log4j2
@Service
public class SheetsClient {

    private final WebClient webClient;
    private final RabbitMessagingTemplate rabbitMessagingTemplate;
    private final ObjectMapper objectMapper;

    public SheetsClient(WebClient.Builder builder,
                        RabbitMessagingTemplate rabbitMessagingTemplate,
                        ObjectMapper objectMapper) {

        this.webClient = builder
                .baseUrl("https://rest.tsheets.com/api/v1")
                .defaultHeaders(httpHeaders -> httpHeaders.setAccept(List.of(MediaType.APPLICATION_JSON)))
                .defaultHeaders(httpHeaders -> httpHeaders.setContentType(MediaType.APPLICATION_JSON))
                .build();
        this.rabbitMessagingTemplate = rabbitMessagingTemplate;
        this.objectMapper = objectMapper;

    }

    public Mono<JsonNode> get(String path, QueryParams params) {
        return this.request(path, params);
    }

    public Flux<JsonNode> fetch(String path, QueryParams params) {
        return this.request(path, params)
                .expand(jsonNode -> {
                    JsonNode nextLinkNode = jsonNode.get("more");
                    if (ObjectUtils.isEmpty(nextLinkNode) || nextLinkNode.isNull()) {
                        return Mono.empty();
                    }
                    boolean next = nextLinkNode.booleanValue();
                    if (next) {
                        params.setPage(params.getPage() + 1);
                        return this.request(path, params);
                    } else {
                        return Mono.empty();
                    }
                })
                .map(jsonNode -> jsonNode.get("results"));
    }

    private Mono<JsonNode> request(String path, QueryParams params) {
        log.debug("Request uri: {}, params: {}", path, params);
        return Mono.subscriberContext().flatMap(ctx -> {
            TokenState tokenState = ctx.get("token");
            ResultSync resultSync = ctx.get("resultSync");
            resultSync.setParams(params);
            resultSync.setElapsedTime(System.currentTimeMillis());
            return this.webClient.get().uri(uriBuilder -> uriBuilder.path(path).queryParams(params).build())
                    .headers(header -> header.setBearerAuth(tokenState.getAccessToken()))
                    .accept(MediaType.APPLICATION_JSON)
                    .retrieve()
                    .bodyToMono(JsonNode.class)
                    .doOnSuccess(resp -> this.handlerSuccessNext(resp, resultSync))
                    .onErrorResume(err -> this.handlerException(err, resultSync));
        });
    }

    private Mono<JsonNode> handlerException(Throwable err, ResultSync resultSync) {
        if (err instanceof ApiException) {
            resultSync.setMsg(((ApiException) err).getMsg());
            resultSync.setStatus(((ApiException) err).getStatus());
        } else {
            resultSync.setMsg(err.getMessage());
            resultSync.setStatus(HttpStatus.INTERNAL_SERVER_ERROR);
        }

        resultSync.setElapsedTime(System.currentTimeMillis() - resultSync.getElapsedTime());

        log.error("Sync {} data error action {}, elapsedTime {}, msg: {}",
                resultSync.getTenantId(), resultSync.getAction(),
                resultSync.getElapsedTime(), resultSync.getMsg());

        this.rabbitMessagingTemplate.convertAndSend(RabbitKeys.BASE_EXCHANGE_NAME,
                RabbitKeys.LOGS_TASK_QUEUE_KEY, resultSync);
        return Mono.error(err);
    }

    private void handlerSuccessNext(JsonNode resp, ResultSync resultSync) {

        JsonNode countNode = resp.get("results");

        if (ObjectUtils.isEmpty(countNode)) {
            resultSync.setCount(resp.size());
        } else {
            resultSync.setCount(countNode.size());
        }

        resultSync.setElapsedTime(System.currentTimeMillis() - resultSync.getElapsedTime());

        log.debug("sync {} data success, action {}, elapsedTime {}, count {}",
                resultSync.getTenantId(), resultSync.getAction(),
                resultSync.getElapsedTime(), resultSync.getCount());

        this.rabbitMessagingTemplate.convertAndSend(RabbitKeys.BASE_EXCHANGE_NAME,
                RabbitKeys.LOGS_TASK_QUEUE_KEY, resultSync);
    }
}
